﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Foundation;
using Foundation.ExtensionMethods;
using Foundation.MarkupExtensions;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Highlighting;
using Poet.Modules;
using Poet.Properties;
using Poet.Views;
using Application = System.Windows.Application;
using Control = System.Windows.Controls.Control;
using Timer = System.Windows.Forms.Timer;

namespace Poet.ViewModels
{
    /// <summary>
    /// Document View Model.
    /// </summary>
    [DataContract]
    internal class DocumentViewModel : RootViewModel, IDisposable
    {
        #region Fields

        private static int _newDocumentCounter;
        public static readonly CompletionManager CompletionManager;
        private static readonly EventWaitHandle SerializeEvent = new ManualResetEvent(true);
        private static readonly Dictionary<string, string> BracketsDictionary = new Dictionary<string, string>
            {
                {"<", ">"},
                {"{", "}"},
                {"[", "]"},
                {"(", ")"},
                {"'", "'"},
                {"\"", "\""}
            };

        private int _currentWordOffset;
        private int _currentWordLength;
        private SpellColorizer _spellColorizer;
        private SpellCheckModule _spellHelper;
        private CompletionWindow _completionWindow;
        private string _previousEnteredText;
        private int _previousCaretOffset;
        private double _scale;   
        private bool _hasTextChanges;
        private Timer _completionListCacheTimer;
        private Timer _searchCacheTimer;
        private List<ICompletionData> _completeList;
        private List<TextEditor> _registredTextEditors;
        private FileSystemWatcher _fileSystemWatcher;
        private Dictionary<string, bool> _canExecuteCache; 

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes the <see cref="DocumentViewModel"/> class.
        /// </summary>
        static DocumentViewModel()
        {
            SyntaxModule.LoadDefaultPalettes();
            Syntaxes = HighlightingManager.Instance.HighlightingDefinitions.ToList();
            var dictionaryFilePath = PathProvider.FindFileByName(Settings.Default.GlobalWordsUsingDictionary);
            CompletionManager = new CompletionManager(dictionaryFilePath, Syntaxes);
            Syntaxes.Sort((x, y) => string.CompareOrdinal(x.Name, y.Name));
            Default = new DocumentViewModel();
            new Thread(UpdateMatchThread) { IsBackground = true, Priority = ThreadPriority.Lowest}.Start();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DocumentViewModel"/> class.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        public DocumentViewModel(string fileName = null)
        {
            FileName = fileName;          
            Initialize();   
        }

        public void Dispose()
        {          
            _spellColorizer.Updated -= OnSpellColorizerOnUpdated;
            _spellColorizer.Dispose();
            _fileSystemWatcher.Dispose();
            ActiveTextEditorView = null;
            TextEditorViewModel.TextArea.SyntaxHighlightingChanged = null;
            CompletionManager.CompletionListUpdated -= OnCompletionListUpdated;
            CompletionManager.SaveDictionary();
            SearchViewModel.PropertyChanged -= OnSearchViewModelOnPropertyChanged;
            TextEditorViewModel.TextChanged -= OnTextEditorViewModelOnTextChanged;
            foreach (var editor in _registredTextEditors)
            {
                editor.TextArea.SelectionChanged -= OnTextAreaOnSelectionChanged;
                editor.TextArea.Caret.PositionChanged -= OnCaretOnPositionChanged;
                editor.PreviewKeyDown -= OnActiveTextEditorViewOnPreviewKeyDown;
                editor.ContextMenuOpening -= OnActiveTextEditorViewOnContextMenuOpening;   
            }
        }

        #endregion

        #region Properties

        [DataMember]
        private FormatSet Format { get; set; }

        [DataMember]
        private PaletteSet Palette { get; set; }

        [DataMember]
        private FontSet Font { get; set; }

        /// <summary>
        /// Gets the default.
        /// </summary>
        public static DocumentViewModel Default { get; private set; }

        /// <summary>
        /// Gets the name of the file.
        /// </summary>
        /// <value>
        /// The name of the file.
        /// </value>
        [DataMember]
        public string FileName
        {
            get { return Get(() => FileName); }
            private set { Set(() => FileName, value); }
        }

        /// <summary>
        /// Gets the text editor view model.
        /// </summary>
        public TextEditor TextEditorViewModel { get; private set; }

        /// <summary>
        /// Gets or sets the text editor.
        /// </summary>
        /// <value>
        /// The text editor.
        /// </value>
        public TextEditor ActiveTextEditorView
        {
            get { return Get(() => ActiveTextEditorView); }
            set { Set(() => ActiveTextEditorView, value); }
        }

        /// <summary>
        /// Gets the spell menu items.
        /// </summary>
        public static ObservableCollection<Control> SpellMenuItems { get; private set; }

        /// <summary>
        /// Gets the syntaxes.
        /// </summary>
        public static List<IHighlightingDefinition> Syntaxes { get; private set; }

        /// <summary>
        /// Gets or sets the syntax.
        /// </summary>
        /// <value>
        /// The syntax.
        /// </value>
        public IHighlightingDefinition Syntax
        {
            get { return Get(() => Syntax); }
            set { Set(() => Syntax, value); }
        }

        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        /// <value>
        /// The title.
        /// </value>
        public string Title
        {
            get { return Get(() => Title); }
            set { Set(() => Title, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [close brackets].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [close brackets]; otherwise, <c>false</c>.
        /// </value>
        public bool CloseBrackets
        {
            get { return Get(() => CloseBrackets); }
            set { Set(() => CloseBrackets, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [use folding].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [use folding]; otherwise, <c>false</c>.
        /// </value>
        public bool UseFolding
        {
            get { return Get(() => UseFolding); }
            set { Set(() => UseFolding, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [use autocomplete].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [use autocomplete]; otherwise, <c>false</c>.
        /// </value>
        public bool UseAutocomplete
        {
            get { return Get(() => UseAutocomplete, true); }
            set { Set(() => UseAutocomplete, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [use highlighting].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [use highlighting]; otherwise, <c>false</c>.
        /// </value>
        public bool UseHighlighting
        {
            get { return Get(() => UseHighlighting); }
            set { Set(() => UseHighlighting, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [use spell].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [use spell]; otherwise, <c>false</c>.
        /// </value>
        public bool UseSpell
        {
            get { return Get(() => UseSpell); }
            set { Set(() => UseSpell, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [detect syntax].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [detect syntax]; otherwise, <c>false</c>.
        /// </value>
        public bool DetectSyntax
        {
            get { return Get(() => DetectSyntax); }
            set { Set(() => DetectSyntax, value); }
        }

        private Regex WholeWordRegex
        {
            get
            {
                return
                    SpellColorizer.SpellCheckers.Any()
                        ? SpellColorizer.SpellCheckers.First().WholeWordRegex
                        : StringExtensions.DefaultWholeWordRegex;
            }
        }

        private string CurrentWord
        {
            get
            {
                var line = ActiveTextEditorView.TextArea.Caret.Line;
                var offset = ActiveTextEditorView.TextArea.Caret.Offset;
                var documentLine = ActiveTextEditorView.TextArea.TextView.Document.GetLineByNumber(line);
                var lineText = ActiveTextEditorView.Text.Substring(documentLine.Offset, documentLine.Length);
                var word = lineText.GetCurrentWord(WholeWordRegex, offset - documentLine.Offset);

                if (word == null)
                {
                    _currentWordLength = 0;
                    _currentWordOffset = 0;
                    return string.Empty;
                }

                _currentWordOffset = documentLine.Offset + word.Index;
                _currentWordLength = word.Length;
                return word.Value;
            }
        }

        private List<int> _matches;

        public List<Match> Matches
        {
            get { return Get(() => Matches); }
            set
            {
                Set(() => Matches, value);
                if (value == null) _matches = null;
                else
                {
                    _matches = new List<int>();
                    value.ForEach(m => _matches.Add(m.Index));
                }
            }
        }

        public int? NearestMatchIndex
        {
            get { return Get(() => NearestMatchIndex); }
            set
            {
                if (Matches == null || !Matches.Any())
                {
                    value = null;
                    Set(() => MatchIndex, null);
                }

                if (MatchIndex == null)
                    value = NearestMatchIndex;

                if (value < 0)
                    value = 0;
                if (Matches != null && value >= Matches.Count)
                    value = Matches.Count - 1;

                Set(() => NearestMatchIndex, value);
                MatchIndex = value;
            }
        }

        public int? MatchIndex
        {
            get { return Get(() => MatchIndex); }
            set
            {
                var textEditor = ActiveTextEditorView;
                if (Matches == null || !Matches.Any() || textEditor == null)
                {
                    value = null;
                    Set(() => NearestMatchIndex, null);
                }

                var oldValue = MatchIndex;
                var newValue = value;

                Set(() => MatchIndex, value);
                if (value != null)
                    SearchViewModel.AddRecentPattern();
             
                if (oldValue == newValue) return;

                if (newValue != null)
                {
                    if (newValue < 0) newValue = 0;
                    if (newValue >= Matches.Count) newValue = Matches.Count - 1;
                    var match = Matches[newValue.Value];
                    if (oldValue == null)
                    {
                        var offset = textEditor.TextArea.Caret.Offset;
                        textEditor.SelectSubstringAndMakeVisible(match.Index, match.Length);
                        textEditor.TextArea.Caret.Offset =
                            offset < match.Index
                                ? textEditor.SelectionEnd
                                : textEditor.SelectionStart;
                    }

                    textEditor.SelectSubstringAndMakeVisible(match.Index, match.Length);
                    if (newValue != oldValue)
                        textEditor.TextArea.Caret.Offset =
                            oldValue < newValue
                                ? textEditor.SelectionEnd
                                : textEditor.SelectionStart;
                }
            }
        }

        #endregion

        #region Methods

        private void SetExecutedHandlers()
        {
            this[ApplicationCommands.Properties].Executed += (o, e) =>
                {
                    var encoding = e.Parameter as Encoding;
                    if (encoding != null)
                    {
                        TextEditorViewModel.Encoding = encoding;
                        TextEditorViewModel.IsModified = true;
                    }

                    if (!Equals(e.Parameter, ":Encoding")) return;
                    var viewModel = Store.OfType<EncodingViewModel>();
                    viewModel.CurrentEncodingInfo =
                        viewModel.EncodingInfos.FirstOrDefault(i => i.CodePage == TextEditorViewModel.Encoding.CodePage);
                    var result = new EncodingView { DataContext = viewModel, Owner = Application.Current.MainWindow }.ShowDialog();
                    if (result == true && viewModel.CurrentEncoding != null) 
                        TextEditorViewModel.Encoding = viewModel.CurrentEncoding;
                };

            this[ApplicationCommands.Close].Executed += (o, e) =>
                {
                    var args = new CancelEventArgs();
                    if (Equals(e.Parameter, "DontSave"))
                        TextEditorViewModel.IsModified = false;
                    OnClosing(o, args);
                    if (args.Cancel) return;
                    Dispose();
                };

            this[ApplicationCommands.Save].Executed += (o, e) =>
                {
                    var result = Save();
                    var cancelEventArgs = e.Parameter as CancelEventArgs;
                    if (cancelEventArgs != null)
                        cancelEventArgs.Cancel = !result;
                };

            this[ApplicationCommands.SaveAs].Executed += (o, e) =>
                {
                    var result = SaveAs();
                    var cancelEventArgs = e.Parameter as CancelEventArgs;
                    if (cancelEventArgs != null)
                        cancelEventArgs.Cancel = !result;
                };

            this[ApplicationCommands.Open].Executed += (o, e) =>
                {
                    if (Equals(e.Parameter, ":Reload"))
                    {
                        var message = Localizing.ProvideValue(@"ReloadFileMessage");
                        var dialogResult = MessageBox.Show(message, Title, MessageBoxButton.OKCancel, MessageBoxImage.Question);
                        if (dialogResult == MessageBoxResult.OK)
                            TryLoadFile(FileName);
                    }

                    Encoding encoding = null;
                    if (Equals(e.Parameter, ":Encoding"))
                    {
                        var viewModel = Store.OfType<EncodingViewModel>();
                        viewModel.CurrentEncodingInfo =
                            viewModel.EncodingInfos.FirstOrDefault(i => i.CodePage == TextEditorViewModel.Encoding.CodePage);
                        var result = new EncodingView {DataContext = viewModel, Owner = Application.Current.MainWindow}.ShowDialog();
                        if (result == true && viewModel.CurrentEncoding != null)
                            encoding = viewModel.CurrentEncoding;
                    }

                    encoding = encoding ?? e.Parameter as Encoding;
                    if (encoding == null) return;
                    if (TextEditorViewModel.IsModified)
                    {
                        var message = FileName == null
                                          ? Localizing.ProvideValue("ReloadMessage")
                                          : Localizing.ProvideValue("ReloadFileMessage");

                        var dialogResult = MessageBox.Show(message, Title, MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
                        if (dialogResult == MessageBoxResult.Cancel) return;
                        if (dialogResult == MessageBoxResult.Yes)
                            Save();
                    }

                    if (FileName == null)
                    {
                        var text = TextEditorViewModel.Text;
                        var bytes = TextEditorViewModel.Encoding.GetBytes(text);
                        TextEditorViewModel.Load(new MemoryStream(bytes), encoding);
                        TextEditorViewModel.IsModified = true;
                    }
                    else
                    {
                        TextEditorViewModel.Load(FileName, encoding);
                    }
                };

            this[ApplicationCommands.PrintPreview].Executed += (o, e) =>
                {
                    var flowDocument = TextEditorViewModel.CreateFlowDocument();
                    PrintModule.ShowPrintPreviewDialog(flowDocument, Title);
                };

            this[ApplicationCommands.Print].Executed += (o, e) =>
                {
                    if (Equals(e.Parameter, null))
                    {
                        var flowDocument = TextEditorViewModel.CreateFlowDocument();
                        PrintModule.Print(flowDocument, Title);
                    }

                    if (Equals(e.Parameter, "PageSetup"))
                        PrintModule.ShowPageSetupDialog();
                };

            this[ApplicationCommands.Find].Executed += (o, e) =>
                {
                    var parameter = e.Parameter;
                    if (parameter == null)
                    {
                        if (!string.IsNullOrEmpty(TextEditorViewModel.SelectedText))
                        {
                            SearchViewModel.TextPattern = TextEditorViewModel.SelectedText;
                            SearchViewModel.RegexPattern = TextEditorViewModel.SelectedText;
                        }

                        ((RoutedUICommand) e.Command).Execute(null, Application.Current.MainWindow);
                        return;
                    }

                    switch (parameter.ToString())
                    {
                        case "Translate":
                        case "Search":
                            var service =
                                Equals(parameter, "Translate")
                                    ? Settings.Default.TranslateService
                                    : Settings.Default.SearchService;
                            var useWord = string.IsNullOrEmpty(ActiveTextEditorView.SelectedText);
                            var text = useWord ? CurrentWord : ActiveTextEditorView.SelectedText;
                            Process.Start(service + text);

                            break;
                        case "GoToLine":
                            var viewModel = new GoToLineViewModel
                                {
                                    MinLineNumber = 1,
                                    MaxLineNumber = TextEditorViewModel.LineCount,
                                    LineNumber = TextEditorViewModel.TextArea.Caret.Line
                                };

                            var view = new GoToLineView {DataContext = viewModel, Title = Title};
                            if (view.ShowDialog() == true)
                                ActiveTextEditorView.TextArea.Caret.Line = viewModel.LineNumber;
                            break;
                        default:
                            var direct = bool.Parse(e.Parameter.ToString());
                            Seek(direct);
                            break;
                    }
                };

            this[ApplicationCommands.Replace].Executed += (o, e) =>
                {
                    var parameter = e.Parameter;
                    if (parameter == null)
                    {
                        if (!string.IsNullOrEmpty(TextEditorViewModel.SelectedText))
                        {
                            SearchViewModel.TextPattern = TextEditorViewModel.SelectedText;
                            SearchViewModel.RegexPattern = TextEditorViewModel.SelectedText;
                        }

                        ((RoutedUICommand)e.Command).Execute(null, Application.Current.MainWindow);
                        return;
                    }

                    var template = SearchViewModel.ReplaceTemplate;
                    switch (parameter.ToString())
                    {
                        case "Single":
                            ActiveTextEditorView.TextArea.Selection.ReplaceSelectionWithText(ActiveTextEditorView.TextArea, template);
                            break;
                        default:
                            var direct = bool.Parse(e.Parameter.ToString());
                            var matchIndex = MatchIndex;
                            _nextMatch = null;
                            if (matchIndex!= null)
                            {
                                if (direct && matchIndex < Matches.Count - 1)
                                    _nextMatch = Matches[matchIndex.Value + 1];
                                if (!direct && matchIndex > 0)
                                    _nextMatch = Matches[matchIndex.Value - 1];
                            }

                            _difference = direct ? template.Length - ActiveTextEditorView.TextArea.Selection.Length : 0;
                            if (ActiveTextEditorView.TextArea.Selection.Length == 0)
                                Seek(direct);
                            else
                            {
                                ActiveTextEditorView.TextArea.Selection.ReplaceSelectionWithText(
                                    ActiveTextEditorView.TextArea, template);
                                UpdateMatches(5);
                            }

                            break;
                    }
                };
        }

        private int _difference;
        private Match _nextMatch;


        private void SetCanExecuteHandlers()
        {
            this[ApplicationCommands.Close].CanExecute += (o, e) =>
                {
                    e.CanExecute = e.Parameter == null || Equals(e.Parameter, "DontSave");
                };

            this[ApplicationCommands.Save].CanExecute += (o, e) =>
                {
                    e.CanExecute = !Equals(e.Parameter, "All") && TextEditorViewModel.IsModified;
                };

            this[ApplicationCommands.Open].CanExecute += (o, e) =>
                {
                    var encoding = e.Parameter as Encoding;
                    if (encoding != null)
                        e.CanExecute = !Equals(TextEditorViewModel.Encoding, encoding) || TextEditorViewModel.IsModified;
                    else if (Equals(e.Parameter, ":Encoding")) e.CanExecute = true;
                    else if (Equals(e.Parameter, ":Reload"))
                        e.CanExecute = !string.IsNullOrEmpty(FileName) && TextEditorViewModel.IsModified;
                };

            this[ApplicationCommands.Find].CanExecute += (o, e) =>
                {
                    var parameter = e.Parameter;
                    if (parameter == null || !string.IsNullOrEmpty(SearchViewModel.Error)) return;

                    switch (parameter.ToString())
                    {
                        case "Translate":
                        case "Search":
                            var useWord = string.IsNullOrEmpty(ActiveTextEditorView.SelectedText);
                            var text = useWord ? CurrentWord : ActiveTextEditorView.SelectedText;
                            e.CanExecute = !string.IsNullOrWhiteSpace(text) && text.Length < 1024;
                            break;
                        case "GoToLine":
                            e.CanExecute = true;
                            break;
                        case "True":
                        case "true":
                        case "False":
                        case "false":
                            var direct = bool.Parse(e.Parameter.ToString());
                            e.CanExecute = Matches != null && CanSeek(direct, "Find" + parameter);
                            return;
                    }
                };

            this[ApplicationCommands.Replace].CanExecute += (o, e) =>
                {
                    var parameter = e.Parameter;
                    if (parameter == null || !string.IsNullOrEmpty(SearchViewModel.Error)) return;

                    if (parameter.ToString().Contains("Next") || parameter.ToString().Contains("Back"))
                    {
                        e.CanExecute =
                            SearchViewModel.UseRegex
                                ? Regex.IsMatch(ActiveTextEditorView.SelectedText, SearchViewModel.RegexPattern)
                                : SearchViewModel.TextPattern.Equals(ActiveTextEditorView.SelectedText);
                        return;
                    }

                    switch (parameter.ToString())
                    {
                        case "Single":
                            e.CanExecute = true;
                            break;
                        case "True":
                        case "true":
                        case "False":
                        case "false":
                            var direct = bool.Parse(e.Parameter.ToString());
                            e.CanExecute = MatchIndex != null || CanSeek(direct, "Replace" + direct);
                            return;
                    }
                };

            this[ApplicationCommands.Properties].CanExecute +=
                (sender, args) => args.CanExecute = !Equals(TextEditorViewModel.Encoding, args.Parameter);
        }

        private void Seek(bool direct)
        {
            if (Matches == null) return;
            if (MatchIndex == null)
            {
                var offset = ActiveTextEditorView.TextArea.Caret.Offset;
                var matchIndex =
                    direct
                        ? Matches.FindIndex(m => m.Index + m.Length >= offset)
                        : Matches.FindLastIndex(m => m.Index <= offset);
                MatchIndex =
                    matchIndex < 0
                        ? direct ? 0 : Matches.Count
                        : matchIndex;
            }
            else
            {
                if (MatchIndex > 0 && !direct) MatchIndex--;
                if (MatchIndex < Matches.Count - 1 && direct) MatchIndex++;
            }
        }

        private bool CanSeek(bool directSearch, string commandKey)
        {
            if (ActiveTextEditorView == null || Matches == null) return false;
            bool canExecute;
            if (_canExecuteCache.TryGetValue(commandKey, out canExecute))
                return canExecute;

            var offset =
                Equals(SearchViewModel.ActiveDocumentView.DataContext, this)
                    ? ActiveTextEditorView.TextArea.Caret.Offset
                    : directSearch && ActiveTextEditorView.Text.Length > 0
                          ? 0
                          : ActiveTextEditorView.Text.Length - 1;

            if (MatchIndex == null)
            {
                var canMove =
                    directSearch
                        ? Matches.Any(m => m.Index + m.Length > offset)
                        : Matches.Any(m => m.Index < offset);
                canExecute = Matches.Any() && canMove;
                _canExecuteCache.Add(commandKey, canExecute);
                return canExecute;
            }

            canExecute = 
                directSearch
                    ? MatchIndex < Matches.Count - 1
                    : MatchIndex > 0;
            _canExecuteCache.Add(commandKey, canExecute);
            return canExecute;
        }

        private void SetPropertyHandlers()
        {
            this[() => UseAutocomplete].PropertyChanged += (sender, args) => UpdateCompletionList(true);

            this[() => Syntax].PropertyChanged += (sender, args) =>
                {
                    TextEditorViewModel.TextArea.SyntaxHighlighting = Syntax;
                    UpdateCompletionList(true);
                    if(Syntax != null) return;
                    UseHighlighting = false;
                    UseFolding = false; 
                    UseSpell = FormatViewModel.DefaultSet.SpellCheck;
                };

            this[() => UseFolding].PropertyChanged += (sender, args) =>
                {
                    TextEditorViewModel.TextArea.UseFolding = UseFolding && Syntax != null;
                };

            this[() => UseSpell].PropertyChanged += (sender, args) =>
                {
                    if(UseSpell) UseHighlighting = false;
                    TextEditorViewModel.TextArea.Spell = UseSpell ? _spellColorizer : null;
                };

            this[() => FileName].PropertyChanged += (sender, args) =>
                {
                    if (string.IsNullOrEmpty(FileName)) return;
                    Title = Path.GetFileName(FileName);
                    _fileSystemWatcher.Path = Path.GetPathRoot(FileName);
                    _fileSystemWatcher.EnableRaisingEvents = true;
                };

            this[() => ActiveTextEditorView].PropertyChanged += (sender, args) =>
            {
                UpdateCompletionList(false);
                _canExecuteCache.Clear();
                SetNearestMatchIndex();
                CheckMatchIndex();
                CommandManager.InvalidateRequerySuggested();
                var textEditor = ActiveTextEditorView;
                if (textEditor == null || _registredTextEditors.Contains(textEditor)) return;
                _registredTextEditors.Add(textEditor);
                textEditor.TextArea.SelectionChanged += OnTextAreaOnSelectionChanged;
                textEditor.TextArea.Caret.PositionChanged += OnCaretOnPositionChanged;
                textEditor.PreviewKeyDown += OnActiveTextEditorViewOnPreviewKeyDown;
                textEditor.ContextMenuOpening += OnActiveTextEditorViewOnContextMenuOpening;
            };

            this[() => UseHighlighting].PropertyChanged += (sender, args) =>
                {
                    if (UseHighlighting)
                    {
                        if (TextEditorViewModel.TextArea.SyntaxHighlighting == null)
                            TextEditorViewModel.TextArea.SyntaxHighlighting = Syntax;
                        if (TextEditorViewModel.TextArea.SyntaxHighlighting == null)
                            UseHighlighting = false;
                        UseSpell = !UseHighlighting;
                    }
                    else TextEditorViewModel.TextArea.SyntaxHighlighting = null;
                };

            this[() => DetectSyntax].PropertyChanged += (sender, args) =>
                {
                    if (!DetectSyntax) return;
                    var fileExtension = Path.GetExtension(FileName);
                    if (!string.IsNullOrEmpty(fileExtension))
                    {
                        Syntax = HighlightingManager.Instance.GetDefinitionByExtension(fileExtension);
                        UseFolding = Syntax != null;
                        UseSpell = Syntax == null;
                    }

                    DetectSyntax = false;
                };
        }

        private void OnActiveTextEditorViewOnContextMenuOpening(object o, ContextMenuEventArgs eventArgs)
        {
            SpellMenuItems.Clear();
            var spellMenuItems = _spellHelper.GenerateSpellMenu(CurrentWord, _currentWordOffset, _currentWordLength);
            SpellMenuItems.AddRange(spellMenuItems);
            RaisePropertyChangedEvent(() => SpellMenuItems);
        }

        private void OnActiveTextEditorViewOnPreviewKeyDown(object o, KeyEventArgs e)
        {
            if (e.Key != Key.Space || Keyboard.Modifiers != ModifierKeys.Control) return;
            ShowCompletionWindow();
            e.Handled = true;
        }

        private void OnCaretOnPositionChanged(object o, EventArgs e)
        {
            if (IsMatchesActual)
                _canExecuteCache.Clear();
            SetNearestMatchIndex();
            UpdateCompletionList(false);
        }

        private void OnTextAreaOnSelectionChanged(object o, EventArgs eventArgs)
        {
            CheckMatchIndex();
        }

        private void SetNearestMatchIndex()
        {
            var textEditor = ActiveTextEditorView;
            if (Matches == null || !Matches.Any() || textEditor == null)
            {
                NearestMatchIndex = null;
                return;
            }

            var offset = textEditor.TextArea.Caret.Offset;  
            var index = _matches.BinarySearch(offset);
            if (index < 0) index = ~index;
            if (index == 0)
            {
                Set(() => NearestMatchIndex, index);
                return;
            }
            if (index == Matches.Count)
            {
                Set(() => NearestMatchIndex, index - 1);
                return;
            }

            if (index > Matches.Count - 1)
            {
                Set(() => NearestMatchIndex, index-1);
                return;
            }
            
            var match1 = Matches[index - 1];
            var match2 = Matches[index];

            var distance1 = offset - (match1.Index + match1.Length);
            var distance2 = match2.Index - offset;

            index = distance1 < distance2 ? index - 1 : index;
            if (distance1 == distance2)
            {
                if (MatchIndex != null) index = MatchIndex.Value;
                else
                    index =
                        (SearchViewModel.RegexOptions & RegexOptions.RightToLeft) == RegexOptions.RightToLeft
                            ? index - 1
                            : index;
            }
            Set(() => NearestMatchIndex, index);
        }

        private void CheckMatchIndex()
        {
            var textEditor = ActiveTextEditorView;
            if (Matches == null || textEditor == null)
            {
                MatchIndex = null;
                return;
            }

            var offset = textEditor.SelectionStart;
            var length = textEditor.SelectionLength;
            var match = Matches.FirstOrDefault(m => m.Index == offset && m.Length == length);
            MatchIndex = match == null ? null : (int?) Matches.IndexOf(match);
        }

        private void OnCompletionListUpdated(object sender, List<ICompletionData> list)
        {
            if (!Equals(sender, this) || Equals(_completeList, list)) return;
            var generation = GC.GetGeneration(_completeList);
            _completeList = list;
            GC.Collect(generation, GCCollectionMode.Forced);
        }

        private void OnFilePathChanged(object sender, RenamedEventArgs args)
        {
            if (!FileName.Contains(args.OldFullPath)) return;
            var oldFileName = FileName;
            var newFileName = FileName.Replace(args.OldFullPath, args.FullPath);
            var message = string.Format(
                Localizing.ProvideValue("FilePathHasBeenChangedMessage"), oldFileName, newFileName);
            Application.Current.Dispatcher.BeginInvoke(
                () =>
                MessageBox.Show(Application.Current.MainWindow, message, Title,
                                MessageBoxButton.OK, MessageBoxImage.Information));
            FileName = newFileName;
        }

        private void OnFileChanged(object sender, FileSystemEventArgs args)
        {
            if (args.FullPath != FileName) return;

            if (args.ChangeType == WatcherChangeTypes.Deleted)
            {
                FileName = string.Empty;
                _fileSystemWatcher.EnableRaisingEvents = false;
                var message = string.Format(Localizing.ProvideValue("FileHasBeenDeletedMessage"), args.FullPath);
                Application.Current.Dispatcher.BeginInvoke(() =>
                    {
                        TextEditorViewModel.IsModified = true;
                        if (MessageBox.Show(Application.Current.MainWindow, message, Title,
                                            MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
                            SaveAs();
                    });
            }

            if (args.ChangeType == WatcherChangeTypes.Changed)
            {
                _fileSystemWatcher.EnableRaisingEvents = false;
                Application.Current.Dispatcher.BeginInvoke((ThreadStart) (() => TextEditorViewModel.IsModified = true));
                var message = string.Format(Localizing.ProvideValue("FileHasBeenChangedMessage"), FileName);
                Application.Current.Dispatcher.BeginInvoke(() =>
                    {
                        TextEditorViewModel.IsModified = true;
                        if (MessageBox.Show(Application.Current.MainWindow, message, Title,
                                            MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
                            TryLoadFile(FileName);
                        else _fileSystemWatcher.EnableRaisingEvents = true;
                    });
            }
        }

        public void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (Keyboard.Modifiers == (ModifierKeys.Shift | ModifierKeys.Control))
            {
                var direct = e.Delta < 0;
                if (ActiveTextEditorView.TextArea.Selection.Length == 0)
                    Seek(direct);
                else
                {
                    var template = SearchViewModel.ReplaceTemplate;
                    ActiveTextEditorView.TextArea.Selection.ReplaceSelectionWithText(
                        ActiveTextEditorView.TextArea, template);
                    UpdateMatches();
                }

                var caretLine = ActiveTextEditorView.TextArea.Caret.Line;
                var firstLine = ActiveTextEditorView.TextArea.TextView.VisualLines.First().LastDocumentLine.LineNumber;
                var lastLine = ActiveTextEditorView.TextArea.TextView.VisualLines.Last().FirstDocumentLine.LineNumber;
                e.Handled = !CanSeek(direct, "Replace" + direct) || (firstLine <= caretLine && caretLine <= lastLine);
            }

            if (Keyboard.Modifiers == ModifierKeys.Shift)
            {
                var direct = e.Delta < 0;
                Seek(direct);

                var caretLine = ActiveTextEditorView.TextArea.Caret.Line;
                var firstLine = ActiveTextEditorView.TextArea.TextView.VisualLines.First().LastDocumentLine.LineNumber;
                var lastLine = ActiveTextEditorView.TextArea.TextView.VisualLines.Last().FirstDocumentLine.LineNumber;
                e.Handled = !CanSeek(direct, "Find" + direct) || (firstLine <= caretLine && caretLine <= lastLine);
            }

            if (Keyboard.Modifiers != ModifierKeys.Control) return;
            _scale += (double) e.Delta/1000;
            var newSize = FontViewModel.DefaultSet.FontSize*_scale;
            if (0 < newSize && newSize < 10000)
                TextEditorViewModel.FontSize = newSize;
            e.Handled = true;
        }

        public void PreviewTextEntered(object sender, TextCompositionEventArgs e)
        {
            if (CloseBrackets && BracketsDictionary.ContainsValue(e.Text) &&
                BracketsDictionary.ContainsKey(_previousEnteredText) &&
                BracketsDictionary[_previousEnteredText] == e.Text &&
                _previousCaretOffset == TextEditorViewModel.TextArea.Caret.Offset)
            {
                _previousEnteredText = string.Empty;
                e.Handled = true;
                return;
            }

            if (CloseBrackets && BracketsDictionary.ContainsKey(e.Text))
            {
                if (e.Text == "<" && Syntax != null && !Syntax.Name.ToLower().Contains("ml"))
                    return;
                var offset = ActiveTextEditorView.TextArea.Caret.Offset;
                ActiveTextEditorView.TextArea.Document.Insert(offset, BracketsDictionary[e.Text]);
                ActiveTextEditorView.TextArea.Caret.Offset = offset;
                _previousCaretOffset = offset + 1;
                _previousEnteredText = e.Text;
                return;
            }

            if (!UseAutocomplete) return;

            if (WholeWordRegex.Match(e.Text).Value != e.Text)
            {
                if (_completionWindow != null)
                {
                    _completionWindow.Hide();
                    //_completionWindow = null;
                }
                return;
            }

            if (e.Text.Split().Any() && _completionWindow == null) ShowCompletionWindow();
            
            if (!e.Text.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries).Any() && _completionWindow != null)
                _completionWindow.Hide();

            _previousCaretOffset = ActiveTextEditorView.TextArea.Caret.Offset;
            _previousEnteredText = e.Text;
        }

        private void ShowCompletionWindow()
        {
            var textEditor = ActiveTextEditorView;
            var completionList = _completeList;
            if (completionList == null || textEditor == null) return;
            _completionWindow = _completionWindow ?? new CompletionWindow(textEditor.TextArea);
            _completionWindow.Closed += delegate { _completionWindow = null; };
            _completionWindow.CompletionList.CompletionData.Clear();
            _completionWindow.CompletionList.CompletionData.AddRange(completionList);
            _completionWindow.Show();
        }

        private void SyntaxHighlightingChanged()
        {
            if (TextEditorViewModel.TextArea.SyntaxHighlighting == null)
            {
                UseHighlighting = false;
                return;
            }

            Syntax = TextEditorViewModel.TextArea.SyntaxHighlighting;
            TextEditorViewModel.TextArea.SetSyntax(Syntax.Name);
            CloseBrackets = true;
            UseHighlighting = true;
            this.DelayCall(20, () =>
                {
                    UseFolding = false;
                    UseFolding = TextEditorViewModel.TextArea.FoldingStrategy != null;
                });
        }

        private void UpdateCompletionList(bool textChanged, int delay = 0)
        {
            if (textChanged)
                _hasTextChanges = true;

            if (!UseAutocomplete)
                return;

            if (_completionWindow != null && _completionWindow.IsVisible)
                return;

            if (delay > 0)
            {
                _completionListCacheTimer.Enabled = false;
                _completionListCacheTimer.Interval = delay;
                _completionListCacheTimer.Enabled = true;
            }

            var syntaxName = Syntax == null ? null : Syntax.Name;
            var textEditor = ActiveTextEditorView;
            if (textEditor == null) return;
            CompletionManager
                .UpdateCompletionList(this, textEditor.Text, textEditor.TextArea.Caret.Offset,
                                      _hasTextChanges, WholeWordRegex, syntaxName);
            _hasTextChanges = false;
        }

        private bool TryLoadFile(string fileName)
        {
            try
            {
                var encoding = EncodingModule.DetectBestEncoding(fileName);
                TextEditorViewModel.Load(fileName, encoding);
                _fileSystemWatcher.EnableRaisingEvents = true;
                Title = Path.GetFileName(fileName);
                RecentFileManagerViewModel.Add(fileName);
                return true;
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message);
                return false;
            }
        }

        private static string GetSaveDialogFilterString()
        {
            var filter =
                AssociationViewModel.DefaultItems.
                                     Aggregate(string.Empty,
                                               (current, item) =>
                                               current +
                                               string.Format("{1} (*{0})|*{0}|", item.ExtensionInfo.Extension,
                                                             item.ProgramInfo.Description));

            filter += "All files (*.*)|*.*";
            return filter;
        }

        private bool SaveAs()
        {
            SaveFileDialog.Filter = GetSaveDialogFilterString();
            SaveFileDialog.FileName = Title;
            if (SaveFileDialog.ShowDialog() != true)
                return false;

            FileName = SaveFileDialog.FileName;
            DetectSyntax = true;
            return Save();
        }

        private bool Save()
        {
            try
            {
                if (string.IsNullOrEmpty(FileName))
                    return SaveAs();

                _fileSystemWatcher.EnableRaisingEvents = false;
                TextEditorViewModel.Save(FileName);
                this.DelayCall(80, () =>
                    {
                        try
                        {
                            _fileSystemWatcher.EnableRaisingEvents = true;
                        }
                        catch
                        {
                            _fileSystemWatcher = null;
                        }
                    });
                RecentFileManagerViewModel.Add(FileName);
                return true;
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message);
                return SaveAs();
            }
        }

        [OnSerializing]
        private void Prepare(StreamingContext context = default(StreamingContext))
        {
            Application.Current.Dispatcher.BeginInvoke(() =>
                {
                    Format = this.GetCommon();              
                    Font = TextEditorViewModel.GetFont();
                    Palette = TextEditorViewModel.GetPalette();
                    Palette.Freeze();
                    SerializeEvent.Set();
                });
            SerializeEvent.Reset();
            SerializeEvent.WaitOne();
        }

        [OnDeserialized]
        private void Initialize(StreamingContext context = default(StreamingContext))
        {
            _scale = 1;
            _registredTextEditors = new List<TextEditor>();
            _canExecuteCache = new Dictionary<string, bool>();
            _previousEnteredText = string.Empty;
            _hasTextChanges = true;
            
            SetPropertyHandlers();
            SetCanExecuteHandlers();
            SetExecutedHandlers();
          
            TextEditorViewModel = new TextEditor {ShowLineNumbers = true, Name = "TextEditorViewModel"};
            TextEditorViewModel.TextArea.SyntaxHighlightingChanged += SyntaxHighlightingChanged; 
 
            _spellColorizer = new SpellColorizer(TextEditorViewModel.TextArea);
            _spellHelper = new SpellCheckModule(TextEditorViewModel.TextArea.Document, _spellColorizer);
            SpellMenuItems = new ObservableCollection<Control>(); 
            _spellColorizer.Updated += OnSpellColorizerOnUpdated;
           
            Format = Format ?? FormatViewModel.DefaultSet;
            Palette = Palette ?? PaletteViewModel.DefaultSet;
            Font = Font ?? FontViewModel.DefaultSet; 
            TextEditorViewModel.SetPalette(Palette);
            TextEditorViewModel.SetFont(Font);
            this.SetCommon(Format);

            _fileSystemWatcher =
                new FileSystemWatcher
                    {
                        NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName,
                        IncludeSubdirectories = true,
                        Path = Path.GetPathRoot(FileName)
                    };
            _fileSystemWatcher.Renamed += OnFilePathChanged;
            _fileSystemWatcher.Changed += OnFileChanged;
            _fileSystemWatcher.Deleted += OnFileChanged;
            
            DetectSyntax = true;
            CloseBrackets = true;
            Title =
                !string.IsNullOrEmpty(FileName) && TryLoadFile(FileName)
                    ? Path.GetFileName(FileName)
                    : Localizing.ProvideValue("New") + _newDocumentCounter++;

            if (string.IsNullOrEmpty(FileName)) UseSpell = true;

            _searchCacheTimer = new Timer { Interval = 256, Enabled = false };
            SearchViewModel.PropertyChanged += OnSearchViewModelOnPropertyChanged; 
            _searchCacheTimer.Tick += (o, e) =>
                {
                    _searchCacheTimer.Stop();
                    UpdateMatches();
                };

            _completeList = new List<ICompletionData>();
            _completionListCacheTimer = new Timer { Interval = 100, Enabled = false };
            CompletionManager.CompletionListUpdated += OnCompletionListUpdated;
            _completionListCacheTimer.Tick += (sender, args) =>
            {
                _completionListCacheTimer.Enabled = false;
                UpdateCompletionList(_hasTextChanges);
            };
           
            UpdateCompletionList(true);
            UpdateMatches();

            TextEditorViewModel.TextChanged += OnTextEditorViewModelOnTextChanged;
        }

        private void OnTextEditorViewModelOnTextChanged(object sender, EventArgs args)
        {
            UpdateMatches(512);
            UpdateCompletionList(true, 512);                   
        }

        private void OnSpellColorizerOnUpdated()
        {
            if (ActiveTextEditorView != null)
                ActiveTextEditorView.TextArea.TextView.Redraw();
        }

        private void OnSearchViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (GetPropertyName(() => SearchViewModel.ActiveDocumentView) != args.PropertyName)
                UpdateMatches(128);
        }

        public bool IsMatchesActual
        {
            get { return Get(() => IsMatchesActual); }
            set { Set(() => IsMatchesActual, value); }
        }

        private void UpdateMatches(int delay = 0)
        {
            IsMatchesActual = false;
            TextEditorViewModel.TextArea.Matches = null;
            if (delay > 0)
            {
                _searchCacheTimer.Enabled = false;
                _searchCacheTimer.Interval = delay;
                _searchCacheTimer.Enabled = true;
                return;
            }

            Requests.Push(this);
            SearchEvent.Set();
        }

        private static readonly Stack<DocumentViewModel> Requests = new Stack<DocumentViewModel>(); 

        private void UpdateMatchesBody()
        {
            var matches = Matches;
            var tmp = matches;
            Matches = _staticMatchesTransfer;
            IsMatchesActual = true;

            TextEditorViewModel.TextArea.Matches = _staticMatchesTransfer;

            if (tmp != null)
                GC.Collect(GC.GetGeneration(tmp), GCCollectionMode.Forced);

            if (ActiveTextEditorView != null)
            {
                if (matches != null)
                {
                    if (_nextMatch != null)
                        ActiveTextEditorView.Select(_nextMatch.Index + _difference, _nextMatch.Length);
                    _nextMatch = null;
                }

                SetNearestMatchIndex();
                CheckMatchIndex();
                _canExecuteCache.Clear();
                CommandManager.InvalidateRequerySuggested();
            }
        }

        private static readonly ManualResetEvent SearchEvent = new ManualResetEvent(true);
        private static List<Match> _staticMatchesTransfer;

        private static void UpdateMatchThread()
        {
            do
            {
                SearchEvent.WaitOne();
                if (Requests.Count == 0)
                {
                    SearchEvent.Reset();
                    continue;
                }

                var documentViewModel = Requests.Pop();
                string text = null;
                Application.Current.Dispatcher.Invoke(() => { text = documentViewModel.TextEditorViewModel.Text; });
                _staticMatchesTransfer = documentViewModel.SearchViewModel.GetMatches(text);
                Application.Current.Dispatcher.Invoke(documentViewModel.UpdateMatchesBody);
            } while (SearchEvent != null);
        }

        #endregion
    }
}